/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

import java.util.*;

/**
 *
 * @author me
 */
public class QuarterlySchedule {

    private QuaterlyScheduleListener scheduleListener = null;

    public void addQuarterlyScheduleListener(QuaterlyScheduleListener listener) {
        scheduleListener = listener;
    }

    /**
     * 
     * @param courses       a list of courses to take
     * @param minUnits
     * @param maxUnits      the maximum units to fit per quarter.
     * @return              
     */
    public void largestClassFirst(CourseDescriptor[] courses, int minUnits, int maxUnits) throws Exception {
        Arrays.sort(courses, Collections.reverseOrder());
        boolean[][] conflictLookup = constructConflictLookup(courses);
        boolean[] options = new boolean[courses.length];
        boolean[] emptyList = new boolean[courses.length];
        for(int i=0;i<options.length;i++) {
            options[i] = true;
            emptyList[i] = false;
        }
        permutate(courses, 0, conflictLookup, emptyList, options, 0, minUnits, maxUnits);
    }
    
    private boolean[][] constructConflictLookup(CourseDescriptor[] courses) throws Exception {
        if(scheduleListener==null)
            throw new Exception("No a schedule listener is assigned");
        int s = courses.length;
        boolean[][] conflictTable = new boolean[s][];
        for(int i=0;i<s;i++)
            conflictTable[i] = new boolean[s];
        
        //TODO: make half of the calculation by mirroring the other half by diagnal.
        for(int i=0;i<s;i++) 
            for(int j=0;j<s;j++) {
                conflictTable[i][j] = courses[i].isConflicting(courses[j]);
            }
        return conflictTable;
    }
    
    
    private void permutate(CourseDescriptor[] courses, int startIndex, boolean[][] conflictLookup, boolean[] currentList, boolean[] options, int cumUnits, int minUnits, int maxUnits) throws Exception {

        for(int i=startIndex;i<options.length;i++) {
            if(!options[i] || courses[i].units + cumUnits > maxUnits)
                continue;
            int newCumUnits = cumUnits + courses[i].units;
            if(newCumUnits > maxUnits)
                continue;

            boolean[] newOptions = options.clone();
            boolean[] newCurrentList = currentList.clone();
            newCurrentList[i] = true;
            newOptions[i] = false;

            if(newCumUnits >= minUnits)
                scheduleListener.handleSchedule(courses, newCurrentList, newCumUnits);

            for(int j=0;j<conflictLookup.length;j++) {
                if(conflictLookup[i][j])
                    newOptions[j] = false;
            }
            permutate(courses, i, conflictLookup, newCurrentList, newOptions, newCumUnits, minUnits, maxUnits);
        }
    }

    public static void main(String[] args) throws Exception {
        CourseDescriptor[] courses = {
            new CourseDescriptor("CS 1", 1),
            new CourseDescriptor("CS 2", 2),
            new CourseDescriptor("CS 3", 3),
            new CourseDescriptor("CS 4", 4),
            new CourseDescriptor("CS 5", 5),
            new CourseDescriptor("CS 6", 6),
            new CourseDescriptor("CS 7", 7),
            new CourseDescriptor("CS 8", 8)
        };

        courses[7].addTimePeriod(new TimePeriod(TimePeriod.FRI, 7.5f, 12.0f));
        courses[6].addTimePeriod(new TimePeriod(TimePeriod.FRI, 7.5f, 12.0f));

        QuarterlySchedule sq = new QuarterlySchedule();
        sq.addQuarterlyScheduleListener( new ConsoleQuarterlyScheduleListener());
        sq.largestClassFirst(courses, 12, 20);
    }

    /**
     *  Given a set of courseDescriptions, return a score for time span over the week.
     *  - The higher the score, the more of the time available.
     *  - Time span is evaluated by the following
     *      - # of hours are not occupied: (in total: 24*7)
     *      - # of days not occupied: (in total: 7. will time a weighted factor)
     *      - # of half-days not occupied: (in total: 14. will tims a weighted factor)
     * @param courses
     * @return
     */
    public static int scoreTimeSpan(CourseDescriptor[] courses, int dayFactor, int halfDayFactor) {
        float hours = 24*7;
        boolean[] dayGrid = new boolean[7];
        boolean[] halfDayGrid = new boolean[7*2];
        for(int i=0;i<dayGrid.length;i++)
            dayGrid[i] = true;
        for(int i=0;i<halfDayGrid.length;i++)
            halfDayGrid[i] = true;

        int score = 0;
        for(int i=0;i<courses.length;i++) {
            ArrayList list = courses[i].getDurationList();
            int s = list.size();
            for(int j=0;j<s;j++) {
                TimePeriod tp = (TimePeriod)list.get(j);
                int start = (int)Math.ceil(tp.startTime);
                int end = (int)Math.floor(tp.endTime);
                dayGrid[tp.day] = false;
                halfDayGrid[tp.day*2] = start <12;
                halfDayGrid[tp.day*2+1] = end >= 12;
                hours -= end-start;
            }
        }
        int dayCount=0, halfDayCount=0;
        for(int i=0;i<dayGrid.length;i++)
            if(dayGrid[i])
                ++dayCount;
        for(int i=0;i<halfDayGrid.length;i++)
            if(halfDayGrid[i])
                ++halfDayCount;

        score = (int)(hours + dayFactor * dayCount + halfDayFactor * halfDayCount);
        return score;
    }


    /**
     *  Standard setup for time span measurement:
     *      - days, then half days, then hours
     * @param courses
     * @return
     */
    public static int scoreTimeSpanDayFirst(CourseDescriptor[] courses) {
        return scoreTimeSpan(courses, 14*7*24, 7*24);
    }

    /**
     * A setup for time span measurement to :
     *      - half days, hours
     * @param courses
     * @return
     */
    public static int scoreTimeSpanHalfDayFirst(CourseDescriptor[] courses) {
        return scoreTimeSpan(courses, 7*24, 7*24*7);
    }
}

class ConsoleQuarterlyScheduleListener implements QuaterlyScheduleListener {

    public void handleSchedule(CourseDescriptor[] courses, boolean[] selected, int cumUnits) throws Exception {
        System.out.println("--------------------------");
        for(int i=0;i<selected.length;i++) {
            if(selected[i]) {
                System.out.println(courses[i].classId + "   " + courses[i].units + " units");
            }
        }
        //Thread.sleep(1000);
    }
    
}



